# coding: utf8
"""
通用日期处理工具
"""

import calendar
import datetime as dt
import time
from datetime import datetime, timedelta
from typing import Generator
from dateutil.tz import tzlocal
from dateutil.relativedelta import relativedelta


class DateTime(datetime):
    """日期工具类"""

    # 当前日期字符串
    @classmethod
    def now_str(cls, time_format=True):
        if time_format:
            return cls.now().strftime("%Y-%m-%d %H:%M:%S")
        return cls.now().strftime("%Y%m%d%H%M%S%f")

    @classmethod
    def date_str(cls):
        return cls.now().strftime("%Y-%m-%d")

    # 3位精度数日期时间字符串
    @classmethod
    def now_float3(cls):
        return cls.now().__str__()[:-3]

    # 当前时间戳浮点型
    @classmethod
    def time_float(cls, ):
        return time.time()

    # 当前时间戳整数
    @staticmethod
    def time_int():
        return int(time.time())

    # 本周/月第一天和最后一天
    @staticmethod
    def today():
        return dt.date.today()

    @classmethod
    def yesterday(cls):
        return cls.today() - timedelta(1)

    @classmethod
    def tomorrow(cls):
        return cls.today() + timedelta(1)

    @classmethod
    def year(cls):
        return cls.today().year

    @classmethod
    def month(cls):
        return cls.today().month

    @classmethod
    def week_first(cls):
        return cls.today() - timedelta(cls.today().weekday())

    @classmethod
    def week_last(cls):
        return cls.today() + dt.timedelta(6 - cls.today().weekday())

    @classmethod
    def month_first(cls):
        return dt.date(cls.year(), cls.month(), 1)

    @classmethod
    def month_last(cls):
        return dt.date(
            cls.year(), cls.month(), calendar.monthrange(cls.year(), cls.month())[1])

    @classmethod
    def timedelta_day(cls, date=None, days=0, string=False):
        if isinstance(date, str):
            date = cls.strToDate(strDate=date)
        if date is None:
            date = cls.now()
        date = date + timedelta(days=days)
        if string:
            return date.strftime("%Y-%m-%d")
        return date

    @classmethod
    def timedelta_datetime(cls, _datetime=None, string=False, **kwargs):
        if _datetime and isinstance(_datetime, str):
            _datetime: datetime = cls.str_to_datetime(_datetime)
        if not _datetime:
            _datetime = cls.now()
        new_datetime = _datetime + timedelta(**kwargs)
        if string:
            return new_datetime.strftime("%Y-%m-%d %H:%M:%S")
        return new_datetime

    @classmethod
    def str_to_datetime(cls, datetime_str: str, default=None) -> [datetime, None]:
        """时间字符串转时间对象"""
        if not datetime_str:
            return cls.now()
        for _format in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M",
                        "%Y-%m-%d %H", "%Y-%m-%d"):
            try:
                _datetime = datetime.strptime(datetime_str, _format)
                return _datetime
            except:
                continue
        return default if default else cls.now()

    @classmethod
    def datetimeToStr(cls, _datetime: datetime, year=False, month=False, week=False,
                      day=False, hour=False, minute=False, millisecond=False) -> str:
        """
        日期时间对象转字符串, 年:月:日 时:分:秒
        :param _datetime: 日期时间
        :param data: 输出精确到日
        :param hour: 输出精确到时
        :param minute: 输出精确到时分
        :return: 日期时间字符串
        """
        if year:
            return _datetime.strftime("%Y")
        if month:
            return _datetime.strftime("%Y-%m")
        if week:
            # if _datetime.strftime("%W/%Y") == datetime.now().strftime("%W/%Y"):
            #     return "本周"
            wStr = int(_datetime.strftime("%W")) + 1
            yStr = _datetime.strftime("%Y")
            weekStr = f"{yStr}-{wStr}"
            return weekStr
        if day:
            return _datetime.strftime("%Y-%m-%d")
        if hour:
            return f"""{_datetime.strftime("%Y-%m-%d %H")}时"""
        if minute:
            return _datetime.strftime("%Y-%m-%d %H:%M")
        if millisecond:
            return _datetime.strftime("%Y-%m-%d %H:%M:%S.%f")
        return _datetime.strftime("%Y-%m-%d %H:%M:%S")

    @classmethod
    def getLatestDate(cls, week=False, month=False, third_week=False, month_week=False,
                      third_month=False, six_month=False, year_month=False,
                      several_week=False, several_month=False, is_add=False,
                      last_week=False, yearly=False, weeks=1, months=0):
        """获取近期日期"""
        start_stat_date = datetime.strptime("2020-05-18", "%Y-%m-%d")  # 统计开始日期
        _today = dt.date.today()
        last_day = _today - timedelta(days=1)
        # 默认近一周, 多加一天的原因：返回的结束时间是当天，所以多加一天
        week_first = _today - relativedelta(weeks=weeks) \
            if is_add else _today - relativedelta(weeks=weeks) + timedelta(days=1)
        week_third = _today - timedelta(days=21)  # 近三周
        month_first = _today - relativedelta(months=months)  # 默认近一月
        # 前2个月
        before_month_2 = _today - relativedelta(months=2)
        # 前5个月
        before_month_5 = _today - relativedelta(months=5)
        # 前11个月
        before_month_11 = _today - relativedelta(months=11)
        if week or several_week:
            return week_first, _today
        elif month or month_week or several_month:
            return month_first, _today
        elif third_week:
            return week_third, _today
        elif last_week:
            return week_first, last_day
        elif yearly:
            return start_stat_date, _today
        if third_month:
            # 近3个月(前2个月和本月)
            start_date = dt.date(before_month_2.year, before_month_2.month, 1)
            return start_date, cls.month_last
        if six_month:
            # 近6个月(前5个月和本月)
            start_date = dt.date(before_month_5.year, before_month_5.month, 1)
            return start_date, cls.month_last
        if year_month:
            # 近12个月(前11个月和本月)
            start_date = dt.date(before_month_11.year, before_month_11.month, 1)
            return start_date, cls.month_last
        return _today

    @classmethod
    def dateStrToTimestamp(cls, dateStr: str, days=0) -> float:
        """
        日期字符串转时间戳
        :param dateStr: 日期字符串, 示例:2019-07-11
        :param days: 日期变动天数, 正数往后, 负数往前, 示例:1 或者 -1
        :return: 时间戳浮点
        """
        date = datetime.strptime(dateStr, "%Y-%m-%d") + timedelta(days=days)
        return time.mktime(date.timetuple())

    @classmethod
    def timestampToDateStr(cls, _timestamp: (int, float)) -> str:
        """
        时间戳转日期字符串
        :param _timestamp: 时间戳数字或浮点数字, 示例:1562806800.0
        :return: 日期字符串  示例:2019-07-11
        """
        return datetime.fromtimestamp(_timestamp).strftime("%Y-%m-%d")

    @classmethod
    def dateRangeStr(cls, startDateStr: str, endDateStr: str, toDict=False) -> Generator:
        """
        日期范围内的日期字符串
        :param startDateStr: 起始日期字符串
        :param endDateStr: 结束日期字符串
        :param toDict: 输出为日期字符串字典列表
        :return: 日期字符串列表或key="Date"的字典 生成器
        """
        startDate = datetime.strptime(startDateStr, "%Y-%m-%d")
        endDate = datetime.strptime(endDateStr, "%Y-%m-%d")

        def date_range(start, end, step):
            while start <= end:
                dataStr = start.strftime("%Y-%m-%d")
                yield {"Date": dataStr} if toDict else dataStr
                start += step

        return date_range(startDate, endDate, timedelta(days=1))

    @classmethod
    def weekRangeStr(cls, startDateStr: str, endDateStr: str, toDict=False) -> Generator:
        """
        周范围内的周字符串
        :param startDateStr: 起始日期字符串
        :param endDateStr: 结束日期字符串
        :param toDict: 输出为周字符串字典列表
        :return: 周字符串列表或key="Week"的字典 生成器
        """
        startDate = datetime.strptime(startDateStr, "%Y-%m-%d")
        endDate = datetime.strptime(endDateStr, "%Y-%m-%d")

        def week_range(start, end, step):
            while start <= end:
                wStr = int(start.strftime("%W")) + 1
                yStr = start.strftime("%Y")
                weekStr = f"{yStr}-{wStr}"
                yield {"Week": weekStr} if toDict else weekStr
                start += step

        return week_range(startDate, endDate, relativedelta(weeks=1))

    @classmethod
    def monthRangeStr(cls, startDateStr: str, endDateStr: str, toDict=False) -> Generator:
        """
        月范围内的月字符串
        :param startDateStr: 起始日期字符串
        :param endDateStr: 结束日期字符串
        :param toDict: 输出为月字符串字典列表
        :return: 月字符串列表或key="Month"的字典 生成器
        """
        startDate = datetime.strptime(startDateStr, "%Y-%m-%d")
        endDate = datetime.strptime(endDateStr, "%Y-%m-%d")

        def month_range(start, end, step):
            while start <= end:
                monthStr = start.strftime("%Y-%m")
                yield {"Month": monthStr} if toDict else monthStr
                start += step

        return month_range(startDate, endDate, relativedelta(months=1))

    @classmethod
    def datetimeStrToDateStr(cls, datetimeStr: str) -> str:
        """
        日期时间字符串转日期字符串
        :param datetimeStr: 日期时间字符串 年-月-日 时:分:秒
        :return: 日期字符串 年-月-日
        """
        return datetime.strptime(datetimeStr, "%Y-%m-%d %H:%M:%S").date().__str__()

    @classmethod
    def strToDate(cls, strDate: str, second=False, millisecond=False, date=True):
        """
        字符串转为日期
        :param strDate: 字符串
        :return: 日期
        """
        try:
            if second:
                return datetime.strptime(strDate, "%Y-%m-%d %H:%M:%S")
            if millisecond:
                return datetime.strptime(strDate, "%Y-%m-%d %H:%M:%S.%f")
            if (datetime_ := datetime.strptime(strDate, "%Y-%m-%d")) and date:
                return datetime_.date()
            return datetime_
        except:
            return None

    @classmethod
    def timestampToDateTime(cls, _timestamp: [int, float], tz=tzlocal()) -> datetime:
        """
        时间戳转日期时间对象
        :param _timestamp: 时间戳整数
        :return: 日期时间对象
        """
        return datetime.fromtimestamp(_timestamp, tz=tz)

    @classmethod
    def compareDate(cls, earlierDate: datetime, laterDate: datetime):
        """
        比较两个日期大小
        :param earlierDate: 较早的日期
        :param laterDate: 较晚的日期
        :return:
        """
        return earlierDate.timestamp() < laterDate.timestamp()

    @classmethod
    def birthdayToAge(cls, strDate: str):
        """生日转年龄，生日不满一年不加年龄（周岁）"""
        try:
            birthday = cls.strToDate(strDate, date=False)
            today = datetime.now()
            if cls.compareDate(today, birthday):
                return 0
            age = today.year - birthday.year
            if today.month > birthday.month:
                return age
            elif today.month < birthday.month:
                return age - 1
            elif today.month == birthday.month:
                if today.day >= birthday.day:
                    return age
                elif today.day < birthday.day:
                    return age - 1
        except Exception as ex:
            return 0


    @classmethod
    def ageToBirthday(cls, age: int):
        """年龄倒推出生日期"""
        try:
            birthday = datetime.now() + timedelta(days=-365 * age)
            return cls.datetimeToStr(birthday, day=True)
        except Exception as ex:
            return cls.date_str()

    @classmethod
    def birthdayToMonthAge(cls, str_date: str, compare_date=None):
        """生日转月龄，不满一月不算"""
        min_zero = lambda v: v if v > 0 else 0
        try:
            if compare_date and isinstance(compare_date, str):
                compare_date = cls.strToDate(compare_date, date=False)
            if not compare_date:
                compare_date = cls.now()
            birthday = cls.strToDate(str_date, date=False)
            if cls.compareDate(compare_date, birthday):
                return 0
            age = compare_date.today().year - birthday.year
            month = compare_date.today().month - birthday.month
            if age >= 0:
                if month >= 0:
                    return age * 12 + month
                return min_zero(age * 12 - month)
            else:
                if month >= 0:
                    return month
                return min_zero(month)
        except Exception as ex:
            return 0

    @classmethod
    def nginx_time_local(cls, time_local):
        """
        nginx日志里本地时间字符串转日期对象
        :param time_local:  06/May/2020:20:53:24 +0800
        :return: 日期对象
        """
        return datetime.strptime(time_local, "%d/%b/%Y:%H:%M:%S +0800")

    @classmethod
    def es_timestamp_now(cls, year=False, month=False, week=False, day=False):
        """处理当前时间范围, 采用elasticsearch的utc时区格式, 2020-05-07T11:08:55.734Z"""
        _utc = lambda dt: dt.isoformat()[:-3] + "Z"
        _now = datetime.now()
        start_date = _now.strftime("%Y-%m-%d")
        end_date = _now.strftime("%Y-%m-%d")
        if year:
            start_date = _now.strftime("%Y-01-01")
            end_date = _now.strftime("%Y-12-31")
        if month:
            start_date = cls.month_first().strftime("%Y-%m-%d")
            end_date = cls.month_last().strftime("%Y-%m-%d")
        if week:
            start_date = cls.week_first().strftime("%Y-%m-%d")
            end_date = cls.week_last().strftime("%Y-%m-%d")
        sdt = cls.strToDate(f"{start_date} 00:00:00.000001", millisecond=True)
        edt = cls.strToDate(f"{end_date} 23:59:59.999999", millisecond=True)
        return _utc(sdt), _utc(edt)

    @classmethod
    def es_date2utc(cls, _datetime: datetime, start=True):
        """把日期对象转为elasticsearch的utc字符串"""
        _date = _datetime.strftime("%Y-%m-%d")
        _utc = lambda dt: dt.isoformat()[:-3] + "Z"
        if start:
            sdt = cls.strToDate(f"{_date} 00:00:00.000001", millisecond=True)
            return _utc(sdt)
        edt = cls.strToDate(f"{_date} 23:59:59.999999", millisecond=True)
        return _utc(edt)

    @classmethod
    def es_timestamp_date(cls, _datetime=datetime.now(), year=False, month=False,
                          week=False):
        """处理时间范围, 返回日期对象"""
        start_date = _datetime.strftime("%Y-%m-%d")
        end_date = _datetime.strftime("%Y-%m-%d")
        years = _datetime.year
        months = _datetime.month
        week_first = _datetime - timedelta(_datetime.weekday())
        week_last = _datetime + dt.timedelta(6 - _datetime.weekday())
        month_first = dt.date(years, months, 1)
        month_last = dt.date(years, months, calendar.monthrange(years, months)[1])
        if year:
            start_date = _datetime.strftime("%Y-01-01")
            end_date = _datetime.strftime("%Y-12-31")
        if month:
            start_date = month_first.strftime("%Y-%m-%d")
            end_date = month_last.strftime("%Y-%m-%d")
        if week:
            start_date = week_first.strftime("%Y-%m-%d")
            end_date = week_last.strftime("%Y-%m-%d")
        sdt = cls.strToDate(f"{start_date} 00:00:00.000001", millisecond=True)
        edt = cls.strToDate(f"{end_date} 23:59:59.999999", millisecond=True)
        return sdt, edt


if __name__ == "__main__":
    print(DateTime.timedelta_day("1989-01-02", 10, True))
    print(DateTime.birthdayToAge("1989-01-02"))
    print(DateTime.now())
    print(DateTime.now_str())
    print(DateTime.now_float3())
    print([d for d in DateTime.dateRangeStr("2019-07-11", "2019-08-11")])
    print(DateTime.nginx_time_local("06/May/2020:20:53:24 +0800"))
    print(DateTime.es_timestamp_now(week=True))
    print(DateTime.es_timestamp_date(month=True))
    today = dt.date.today()
    week_first = today - timedelta(days=1)
    week_last = today - timedelta(days=7)
    print(today)
    start_date, end_dates = DateTime.getLatestDate(is_add=True, last_week=True)
    date_info = list(DateTime.weekRangeStr(start_date.strftime("%Y-%m-%d"),
                                           end_dates.strftime("%Y-%m-%d")))
    end_date = end_dates - timedelta(days=1)
    print(type(end_date))
    print(type(datetime.now().date()))
    print(DateTime.es_date2utc(datetime.now()))
    print(DateTime.birthdayToMonthAge("2014-02-28"))
